from asynchat import async_chat
from curses.textpad import Textbox
from protocol import *
from Queue import Queue
import asyncore
import curses
import logging
import socket
import thread
import time


logging.basicConfig(filename="log.log", level=logging.DEBUG)


class Client (async_chat):
    def __init__(self, ip, port):
        async_chat.__init__(self)
        self.buffer = []
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((ip, port))
        self.set_terminator("\r\n")
        self.ip = ip
        self.port = port
        self.channel = "DEFAULT"
        self.dispatch_map = {"name": self.name_command, "who": self.who_command, "join": self.join_command, "msg": self.private_message_command}
        self.command_response_map = {PingCommand: self.ping_command, MsgCommand: self.msg_command, NameResponse: self.name_response, WhoResponse: self.who_response, JoinResponse: self.join_response}
        self.user_input_queue = Queue()
        self.ui = UI(self)

    def run(self):
        logging.debug("starting threads")
        thread.start_new_thread(asyncore.loop, ())
        thread.start_new_thread(self.process_input_queue, ())
        thread.start_new_thread(self.ui.process_output_queue, ())
        thread.start_new_thread(self.ui.run(), ())

    def process_input_queue(self):
        logging.debug("process_input_queue() loop start")
        while True:
            queue_item = self.user_input_queue.get()
            if len(queue_item) > 0:
                if queue_item[0] == "/":
                    self.parse_user_input(queue_item)
                else:
                    self.send_message(MsgCommand("c:" + self.channel, queue_item))
        logging.debug("process_input_queue() loop end")

    def add_user_input(self, text):
        text = text.rstrip()
        logging.debug("adding %s to user input queue" % text)
        self.user_input_queue.put(text)

    def handle_connect(self):
        logging.debug("handle_connect() connected to server")
        self.ui.output_queue.put("Connected to server %s:%s" % (self.ip, self.port))

    def collect_incoming_data(self, incoming_data):
        self.buffer.append(incoming_data)

    def found_terminator(self):
        self.process_data()

    def process_data(self):
        data = "".join(self.buffer)
        self.buffer = []
        try:
            parsed_data = command_parser.parse_string(data)
            if type(parsed_data) in self.command_response_map:
                data_function = self.command_response_map.get(type(parsed_data))
                data_function(parsed_data)
            else:
                self.ui.add_output("Invalid data received from server")
        except ValueError:
            self.ui.add_output("Error parsing data from server")

    def send_message(self, message_to_send):
        self.push(str(message_to_send) + self.get_terminator())

    def parse_user_input(self, string):
        command = string[1:].split(" ")
        if command[0] in self.dispatch_map:
            command_function = self.dispatch_map[command[0]]
            command_arguments = command[1:]
            command_function(*command_arguments)

    def ping_command(self, data):
        self.send_message(PongCommand(time.time()))

    def msg_command(self, data):
        self.ui.add_output(data.message)
        
    def name_command(self, name):
        self.send_message(NameCommand(name))

    def who_command(self):
        self.send_message(WhoCommand())

    def join_command(self, channel):
        self.send_message(JoinCommand(channel))

    def private_message_command(self, *data):
        self.send_message(MsgCommand("u:" + data[0], " ".join(data[1:])))

    def name_response(self, data):
        self.ui.add_output("Your name was changed to %s" % data.name)

    def who_response(self, data):
        self.ui.add_output("Connected users: %s" % data.connected_users)

    def join_response(self, data):
        self.ui.add_output("You joined the channel %s" % data.channel)
        self.channel = data.channel


class UI (object):
    def __init__(self, c):
        self.client = c
        self.y_pos = 1
        self.output_queue = Queue()

    def main_loop(self):
        while True:
            self.draw()
            text = self.chatbox.edit()
            self.input_window.erase()
            self.client.add_user_input(text)

    def process_output_queue(self):
        logging.debug("process_output_queue() loop start")
        while True:
            queue_item = self.output_queue.get()
            if len(queue_item) > 0:
                self.add_output(queue_item)
        logging.debug("process_output_queue() loop end")

    def draw(self):
        self.scr.box(ord("|"), ord("-"))
        while self.output_queue.qsize() > 0:
            self.add_output(self.output_queue.get())
        self.scr.hline(21, 1, "-", 78)
        self.scr.refresh()

    def run(self):
        curses.wrapper(self.run_curses)

    def run_curses(self, screen):
        self.scr = screen
        curses.noecho()
        self.input_window = self.scr.subwin(1, 78, 22, 1)
        self.chatbox = Textbox(self.input_window)
        self.draw()
        self.main_loop()

    def add_output(self, text):
        logging.debug("adding %s to output" % text)
        self.scr.addstr(self.y_pos, 1, text)
        self.y_pos += 1
        self.draw()


if __name__ == "__main__":
    server_info = str(raw_input("Enter server information: "))
    server_info2 = server_info.split(":")
    command_parser = CommandParser()
    client = Client(server_info2[0], int(server_info2[1]))
    client.run()